package top.easyblog.core.support.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Objects;
import java.util.Optional;

/**
 * Redis分布式锁实现
 *
 * @author: frank.huang
 * @date: 2023-12-21 21:09
 */
public class RedisDistributedLock implements DistributedLock {

    private static final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
    private static final int DEFAULT_ACQUIRE_RESOLUTION_MILLIS = 100;

    //Redis key
    private final String key;
    //Redis 操作工具
    private final RedisTemplate<String, Object> redisTemplate;
    //锁超时等待时间
    private final long lockExpireTimeMs;
    //获取锁等待时间
    private final long lockWaitingTimeMs;

    //维护线程自己的锁标记，确保每个线程在解锁的时候不会讲其他线程的锁解掉
    private final ThreadLocal<Boolean> locked = new ThreadLocal<>();


    public RedisDistributedLock(String key, RedisTemplate<String, Object> redisTemplate, long lockExpireTimeMs, long lockWaitingTimeMs) {
        this.key = key;
        this.redisTemplate = redisTemplate;
        this.lockExpireTimeMs = lockExpireTimeMs;
        this.lockWaitingTimeMs = lockWaitingTimeMs;
    }


    @Override
    public boolean lock() throws InterruptedException {
        locked.set(false);
        long timeout = lockWaitingTimeMs;
        while (timeout > 0) {
            //锁过期时间，+1是程序和Redis交互的时间差
            final long lockExpireAt = System.currentTimeMillis() + lockExpireTimeMs + 1;
            boolean success = Optional.ofNullable(redisTemplate.opsForValue().setIfAbsent(key, String.valueOf(lockExpireAt))).orElse(false);
            if (success) {
                locked.set(true);
                logger.info("Get redis lock >>> {}", key);
                return true;
            }

            //锁剩余时间
            long lockExpireRemaining = Optional.ofNullable(redisTemplate.opsForValue().get(key)).map(time -> Long.parseLong((String) time)).orElse(-1L);
            logger.warn("[debug] get lock. key: {}, lockExpireRemaining: {}", key, lockExpireRemaining);
            if (-1 == lockExpireRemaining) {
                // 应对进入循环没拿到锁，但是get之前锁已被释放导致的情况；
                // 立即尝试重新获取锁
                continue;
            }

            if (lockExpireRemaining < System.currentTimeMillis()) {
                //获取锁失败，检查 Redis 中存储的锁的过期时间，如果锁已过期，则尝试原子性地更新锁的过期时间。
                Object retValue = redisTemplate.opsForValue().getAndSet(key, String.valueOf(lockExpireAt));
                if (Objects.nonNull(retValue) && Objects.equals(retValue, lockExpireRemaining)) {
                    locked.set(true);
                    return true;
                }
            }


            timeout -= DEFAULT_ACQUIRE_RESOLUTION_MILLIS;
            //延迟100毫秒，获取失败等待一段时间再尝试
            Thread.sleep(DEFAULT_ACQUIRE_RESOLUTION_MILLIS);
        }

        return false;
    }

    @Override
    public void unlock() {
        if (isLock()) {
            redisTemplate.delete(key);
            locked.set(false);
            locked.remove();
            logger.info("Release redis lock >>> {}", key);
        }
    }

    @Override
    public boolean isLock() {
        return locked.get();
    }

}
